#ifndef __M_SERVER_H__
#define __M_SERVER_H__

#include "util.hpp"
#include "db.hpp"
#include "online_user.hpp"
#include "room.hpp"
#include "matcher.hpp"
#include "session.hpp"
#include "log.hpp"

#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp> // 非ssl/tls加密

#define WWWROOT "./wwwroot"

class gobang_server
{
public:
    /* 初始化成员和设置回调函数 */
    gobang_server(const std::string &host,
                  const std::string &usr,
                  const std::string &pwd,
                  const std::string &db,
                  const uint16_t port = 3306 /* 为0也可，默认设置为3306 */,
                  const std::string &wwwroot = WWWROOT)
        : _web_root(wwwroot), _ut(host, usr, pwd, db, port), _rm(&_ut, &_olu), _mtm(&_rm, &_ut, &_olu), _ssm(&_wssvr)
    {
        // 1. 实例化server对象——_wssvr
        // 2. 设置日志等级
        _wssvr.set_access_channels(websocketpp::log::alevel::none); // 不打印日志
        // 3. 初始化asio调度器
        _wssvr.init_asio();
        _wssvr.set_reuse_addr(true); // 设置启动地址重用
        // 4. 设置回调函数
        _wssvr.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));                                // http请求回调
        _wssvr.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));                              // websocket握⼿成功回调
        _wssvr.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));                            // websocket连接关闭回调
        _wssvr.set_message_handler(std::bind(&gobang_server::wsmessage_callback, this, std::placeholders::_1, std::placeholders::_2)); // websocket消息处理回调
    }

    /* 启动服务器 */
    void start(uint16_t port)
    {
        // 5. 设置监听端口
        _wssvr.listen(port);
        // 6. 开始获取新连接
        _wssvr.start_accept();
        // 7. 启动服务器
        _wssvr.run();
    }

private:
    /* 设置http响应并发送 */
    void set_http_resp(wsserver_t::connection_ptr &conn,
                       const bool result, const websocketpp::http::status_code::value status_code,
                       const std::string &reason = std::string())
    {
        Json::Value resp_json;
        std::string resp_body;
        resp_json["result"] = result;
        resp_json["reason"] = reason;
        json_util::serialize(resp_json, &resp_body);
        conn->set_status(status_code);
        conn->set_body(resp_body);
        conn->append_header("Content-Type", "application/json");
    }

    /* 发送websocketpp响应 */
    void ws_send_resp(wsserver_t::connection_ptr &conn, const Json::Value &resp_json)
    {
        std::string body;
        json_util::serialize(resp_json, &body);
        conn->send(body);

        // DBG_LOG("发送websocket响应：optype[%s]", resp_json["optype"].asCString());
    }

    /* 从Cookie中获取ssid */
    bool get_cookie_value(const std::string &cookie_str, const std::string &key, std::string *value)
    {
        // Cookie中信息格式：SSID=XXX; PATH=XXX

        // 1. 以; 作为间隔符，分割字符串，得到Cookie中的一个个信息
        std::string sep = "; ";
        std::vector<std::string> cookie_arr; // 存放键值对字符串形式的数组
        string_util::split(cookie_str, sep, cookie_arr);
        // 2. 对单个信息以=作为分隔符，分割字符串，得到单个信息的key和value
        for (auto &str : cookie_arr)
        {
            std::vector<std::string> single_strs;
            string_util::split(str, "=", single_strs);
            if (single_strs.size() != 2) // 非key=value格式
            {
                continue;
            }
            if (single_strs[0] == key)
            {
                *value = single_strs[1];
                return true;
            }
        }
        return false;
    }

private:
    /* 静态资源请求处理 */
    void file_handle(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取请求uri资源路径——请求资源的名字
        websocketpp::http::parser::request req = conn->get_request(); // 获取请求信息
        std::string uri = req.get_uri();
        // 2. 组合成实际资源路径——相对根路径+请求uri
        std::string pathname = _web_root + uri;
        // DBG_LOG("req_uri: %s, req_pathname: %s", uri.c_str(), pathname.c_str());// test
        // 3. 若实际资源路径是目录（路径结尾是"/"），就尾部增加login.html
        if (pathname.back() == '/')
            pathname += "/login.html";
        // 4. 获取资源
        std::string body;
        bool ret = file_util::read(pathname, &body);
        //      a. 文件不存在，读取文件内容失败，返回404
        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset=\"utf-8\">";
            body += "</head>";
            body += "<body>";
            body += "<h1>NOT FOUND!<h1>";
            body += "</body>";
            body += "</html>";

            // 设置响应正文和响应状态码
            conn->set_status(websocketpp::http::status_code::value::not_found);
            conn->set_body(body);
        }
        else
        {
            // 5. 设置响应正文和响应状态码
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::value::ok);
            // 无需设置响应状态码，一般会默认设置为200-ok——ERROR!非200！
        }
    }

    /* 功能性请求处理 */
    /* 用户注册功能请求处理 */
    void reg_handle(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取请求正文
        std::string req_body = conn->get_request_body();
        // 2. 对请求正文进行json反序列化
        Json::Value req_json;
        // Json::Value resp_json;
        // std::string resp_body;
        bool ret = json_util::unserialize(req_body, &req_json);
        if (ret == false)
        {
            DBG_LOG("请求正文格式错误!");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "请求正文格式错误!");
            return;
        }
        // 3. 数据库中新增用户
        if (req_json["username"].isNull() || req_json["password"].isNull())
        {
            DBG_LOG("用户名或密码为空！");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "用户名或密码为空！");
            return;
        }
        //    a. 新增成功，返回200
        if (_ut.add(req_json))
            set_http_resp(conn, true, websocketpp::http::status_code::value::ok, "注册成功！");
        //    b. 新增失败，用户已存在，返回400
        else
        {
            DBG_LOG("用户名已存在！");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "用户名已存在！");
        }
    }
    /* 用户登录请求处理 */
    void login_handle(wsserver_t::connection_ptr &conn)
    {
        // 1. 获取请求正文
        std::string req_body = conn->get_request_body();
        // 2. 对请求正文进行反序列化，并校验请求正文的完整性
        Json::Value req_json;
        bool ret = json_util::unserialize(req_body, &req_json);
        if (ret == false)
        {
            DBG_LOG("请求正文格式错误!");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "请求正文格式错误!");
            return;
        }
        // 3. 校验用户名密码不为空
        if (req_json["username"].isNull() || req_json["password"].isNull())
        {
            DBG_LOG("用户名或密码为空！");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "用户名或密码为空！");
            return;
        }
        // 4. 校验身份
        //    a. 验证失败，返回400
        if (_ut.login(req_json) == false)
        {
            DBG_LOG("用户名或密码错误！");
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "用户名或密码错误！");
            return;
        }
        // 5. 验证通过，为客户端创建session，并设置session的生命周期
        uint64_t uid = req_json["id"].asUInt64();
        session_ptr ssp = _ssm.create_session(uid, ss_status::LOGIN);
        if (ssp.get() == nullptr)
        {
            DBG_LOG("为用户[uid:%lu]创建会话失败！", uid);
            set_http_resp(conn, false, websocketpp::http::status_code::value::internal_server_error, "为用户创建会话失败！");
        }
        _ssm.set_session_expired_time(ssp->ssid(), SESSION_TIMEOUT /* 30s */);
        // 6. 设置http响应头部：Set-Cookie，将session id通过Cookie返回
        std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());
        conn->append_header("Set-Cookie", cookie_ssid);
        set_http_resp(conn, true, websocketpp::http::status_code::value::ok, "登陆成功！");
    }

    /* 用户信息获取功能请求处理 */
    void info_handle(wsserver_t::connection_ptr &conn)
    {
        /* 获取用户信息展示在游戏大厅中 */

        // 1. 获取请求信息中的Cookie，从Cookie中获取ssid
        std::string cookie_str = conn->get_request_header("Cookie");
        // 1.1 若没有Cookie，返回错误信息：请重新登录。
        if (cookie_str.empty())
        {
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "找不到Cookie信息，请重新登录！");
            return;
        }
        // 1.2 有Cookie，Cookie中没有ssid，返回错误信息：请重新登录
        std::string ssid_str;
        bool ret = get_cookie_value(cookie_str, "SSID" /* 这里要和登陆时设置的相照应 */, &ssid_str);
        if (ret == false)
        {
            // 用户重新登录
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "找不到会话id信息，请重新登录！");
            return;
        }
        // 2. 根据ssid获取session，从session中获取用户信息（uid）
        session_ptr ssp = _ssm.get_session_by_ssid(std::stoul(ssid_str));
        // 2.1 没有找到session，认为登陆已过期，需要重新登陆
        if (ssp.get() == nullptr)
        {
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "登陆过期，请重新登录！");
            return;
        }
        // 2.2 获取uid
        uint64_t uid = ssp->get_uid();
        // 3. 根据uid从数据库中取得用户信息，序列化发送给客户端
        Json::Value user_info;
        // 3.1 获取用户信息失败，返回错误：找不到用户信息，请重新登录
        if (_ut.select_by_id(uid, user_info) == false)
        {
            set_http_resp(conn, false, websocketpp::http::status_code::value::bad_request, "找不到用户信息，请重新登录！");
            return;
        }
        // 3.2 序列化发送给客户端
        std::string body;
        json_util::serialize(user_info, &body);
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::value::ok);
        conn->append_header("Content-Type", "application/json");
        // 4. 刷新session的过期时间
        _ssm.set_session_expired_time(ssp->ssid(), SESSION_TIMEOUT);
    }

    /* http请求回调函数 */
    void http_callback(websocketpp::connection_hdl hdl)
    {
        // 获取请求
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();

        // 用户注册功能请求
        if (method == "POST" && uri == "/reg")
        {
            reg_handle(conn);
        }
        // 用户登录功能请求
        else if (method == "POST" && uri == "/login")
        {
            login_handle(conn);
        }
        // 用户信息获取功能请求
        else if (method == "GET" && uri == "/info")
        {
            info_handle(conn);
        }
        // 静态资源请求
        else
        {
            file_handle(conn);
        }
    }

    session_ptr get_session_by_ssid(wsserver_t::connection_ptr conn, Json::Value &err_resp)
    {
        // 1. 登录验证
        // 1.0 获取请求信息中的Cookie，从Cookie中获取ssid
        std::string cookie_str = conn->get_request_header("Cookie");
        // 1.1 若没有Cookie，返回错误信息：请重新登录。
        if (cookie_str.empty())
        {
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "没有找到Cookie，请重新登录！";

            return session_ptr();
        }
        // 1.2 有Cookie，Cookie中没有ssid，返回错误信息：请重新登录
        std::string ssid_str;
        bool ret = get_cookie_value(cookie_str, "SSID" /* 这里要和登陆时设置的相照应 */, &ssid_str);
        if (ret == false)
        {
            // 用户重新登录
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "找不到会话id信息，请重新登录！";

            return session_ptr();
        }
        // 1.3 根据ssid获取session，从session中获取用户信息（uid）
        session_ptr ssp = _ssm.get_session_by_ssid(std::stoul(ssid_str));
        // 1.4 没有找到session，认为登陆已过期，需要重新登陆
        if (ssp.get() == nullptr)
        {
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "登陆过期，请重新登录！";

            return session_ptr();
        }

        return ssp;
    }

    /* 游戏大厅长连接建立成功的回调 */
    void wsopen_game_hall(wsserver_t::connection_ptr conn)
    {
        /* 游戏大厅长连接建立成功 */

        // 1. 登录验证
        Json::Value err_resp;
        session_ptr ssp = get_session_by_ssid(conn, err_resp);
        if (ssp.get() == nullptr) // 登陆失败
        {
            return ws_send_resp(conn, err_resp);
        }

        // 2. 判断客户端是否重新登录——在线
        // 因为用户只有在websocket长连接建立成功时才会加入到游戏大厅。如果已经加入游戏大厅or游戏房间，那就不要重复加入
        uint64_t uid = ssp->get_uid();
        if (_olu.is_in_hall(uid) || _olu.is_in_room(uid))
        {
            err_resp["optype"] = "hall_ready";
            err_resp["result"] = false;
            err_resp["reason"] = "请勿重复登录！";

            return ws_send_resp(conn, err_resp);
        }

        // 3. 将当前客户端和连接添加到游戏大厅中
        _olu.enter_hall(uid, conn);

        // 4. 给客户端发送游戏大厅连接建立成功的响应
        Json::Value resp_json;
        resp_json["optype"] = "hall_ready";
        resp_json["result"] = true;

        ws_send_resp(conn, resp_json);

        // 5. 设置session的生命周期为永久存在——因为是长连接，总不能过了一段时间就掉线
        _ssm.set_session_expired_time(ssp->ssid(), SESSION_FOREVER);
    }
    /* 游戏房间长连接建立成功的回调 */
    void wsopen_game_room(wsserver_t::connection_ptr conn)
    {
        /* 游戏房间长连接建立成功 */

        // 1. 获取当前客户端的session——判断是哪个用户
        Json::Value resp_json;
        session_ptr ssp = get_session_by_ssid(conn, resp_json);
        if (ssp.get() == nullptr) // 登陆失败
        {
            resp_json["optype"] = "room_ready"; // get_session_by_ssid()设置rr_resp["optype"] = "hall_ready"
            return ws_send_resp(conn, resp_json);
        }
        // 2. 判断当前用户是否已经在游戏大厅或其他游戏房间中——在线用户管理
        uint64_t uid = ssp->get_uid();
        if (_olu.is_in_hall(uid) || _olu.is_in_room(uid))
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "请勿重复登录！";

            return ws_send_resp(conn, resp_json);
        }
        // 3. 判断是否已经为其创建了游戏房间——房间管理
        room_ptr rmp = _rm.get_room_by_uid(ssp->get_uid());
        // std::cout << "根据uid:[" << ssp->get_uid() << "]查找房间" << std::endl;// test
        if (rmp.get() == nullptr)
        {
            DBG_LOG("尚未创建房间！");

            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "尚未创建房间！";

            return ws_send_resp(conn, resp_json);
        }
        // 4. 将当前用户添加到在线用户管理中的游戏房间中
        _olu.enter_room(ssp->get_uid(), conn);
        // 5. 将session重新设置为永久存在
        _ssm.set_session_expired_time(ssp->ssid(), SESSION_FOREVER);
        // 6. 向客户端发送响应
        resp_json["optype"] = "room_ready";
        resp_json["result"] = true;
        resp_json["room_id"] = (Json::Value::UInt64)rmp->id();
        resp_json["white_id"] = (Json::Value::UInt64)rmp->get_white_player();
        resp_json["black_id"] = (Json::Value::UInt64)rmp->get_black_player();
        resp_json["self_id"] = (Json::Value::UInt64)ssp->get_uid();
        ws_send_resp(conn, resp_json);
    }
    /* websocket握手成功回调函数 */
    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        /* websocket长连接建立成功后的处理函数 */
        /* 两种长连接：游戏大厅长连接和游戏房间长连接。区分：请求中的路径不同！ */

        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        // 1. 建立游戏大厅的长连接
        if (uri == "/hall")
        {
            wsopen_game_hall(conn);
        }
        // 2. 建立游戏房间的长连接
        else if (uri == "/room")
        {
            wsopen_game_room(conn);
        }
    }

    /* 游戏大厅长连接断开的处理 */
    void wsclose_game_hall(wsserver_t::connection_ptr conn)
    {
        // 1. 登录验证
        Json::Value err_resp;
        session_ptr ssp = get_session_by_ssid(conn, err_resp);
        if (ssp.get() == nullptr)
        {
            return ws_send_resp(conn, err_resp);
        }

        // 2. 将玩家从游戏大厅中移除
        _olu.exit_hall(ssp->get_uid());
        // 3. 重置session的生命周期为有限时间——定时销毁
        _ssm.set_session_expired_time(ssp->ssid());
    }
    /* 游戏房间长连接断开的处理 */
    void wsclose_game_room(wsserver_t::connection_ptr conn)
    {
        // 1. 获取会话信息，识别客户端
        Json::Value resp_json;
        session_ptr ssp = get_session_by_ssid(conn, resp_json);
        if (ssp.get() == nullptr) // 登陆失败
        {
            resp_json["optype"] = "room_ready"; // get_session_by_ssid()设置resp_json["optype"] = "hall_ready"
            return ws_send_resp(conn, resp_json);
        }
        // 2. 将玩家从在线用户管理中的房间中移除——解除玩家和游戏房间长连接的关系绑定
        _olu.exit_room(ssp->get_uid());
        // 3. 重置session的生命周期为有限
        _ssm.set_session_expired_time(ssp->ssid());
        // 4. 将玩家从游戏房间中移除，房间中所有用户都退出就销毁房间
        _rm.remove_room_user(ssp->get_uid());
    }
    /* websocket连接关闭回调函数 */
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
        /* websocket连接断开前的处理 */

        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        // 1. 建立游戏大厅的长连接
        if (uri == "/hall")
        {
            wsclose_game_hall(conn);
        }
        // 2. 建立游戏房间的长连接
        else if (uri == "/room")
        {
            wsclose_game_room(conn);
        }
    }

    /* 游戏大厅中的消息处理 */
    void wsmessage_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
    {
        // 1. 身份验证——当前客户端属于哪个玩家
        Json::Value resp_json;
        session_ptr ssp = get_session_by_ssid(conn, resp_json);
        if (ssp.get() == nullptr) // 登陆失败
        {
            return ws_send_resp(conn, resp_json);
        }

        // 2. 获取请求信息
        std::string msg_value = msg->get_payload();
        Json::Value req_json;
        bool ret = json_util::unserialize(msg_value, &req_json);
        if (ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求信息解析失败！";
            return ws_send_resp(conn, resp_json);
        }

        // test->
        // DBG_LOG("获得用户请求：%s， optype：%s", msg->get_payload(), req_json["optype"].asCString());
        // std::cout << req_json["optype"].asCString() << std::endl;
        // std::cout << req_json["optype"].asString() << std::endl;
        // std::cout << (req_json["optype"].asCString() == "match_start") << std::endl;
        // std::cout << (req_json["optype"].asString() == "match_start") << std::endl;
        // std::cout << strcmp(req_json["optype"].asCString(), "match_start") << std::endl;
        // std::cout << strcmp(req_json["optype"].asString().c_str(), "match_start") << std::endl;
        // std::cout << strcmp(req_json["optype"].asCString(), req_json["optype"].asString().c_str()) << std::endl;

        // 3. 对请求（开始对战匹配and停止对战匹配）处理
        if (!req_json["optype"].isNull())
        {
            // 3.1 开始对战匹配：通过匹配模块，将用户添加到匹配队列中
            if (req_json["optype"].asString() == "match_start") /* 使用asString比较要使用strcmp()，不能使用c++的默认字符串比较原则 */
            {
                _mtm.add_user(ssp->get_uid());
                resp_json["optype"] = "match_start";
                resp_json["result"] = true;

                return ws_send_resp(conn, resp_json);
            }
            // 3.2 停止对战匹配：通过匹配模块，将用户从匹配队列中移除
            else if (req_json["optype"].asString() == "match_stop")
            {
                _mtm.remove_user(ssp->get_uid());
                resp_json["optype"] = "match_stop";
                resp_json["result"] = true;
                return ws_send_resp(conn, resp_json);
            }
        }
        resp_json["optype"] = "unknown";
        resp_json["result"] = false;
        resp_json["reason"] = "请求类型错误！";
        return ws_send_resp(conn, resp_json);
    }
    /* 游戏房间中的消息处理 */
    void wsmessage_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
    {
        // 1. 获取会话信息，识别客户端
        Json::Value resp_json;
        session_ptr ssp = get_session_by_ssid(conn, resp_json);
        if (ssp.get() == nullptr) // 登陆失败
        {
            DBG_LOG("房间-没有找到会话信息！");
            return ws_send_resp(conn, resp_json);
        }
        // 2. 获取客户端的房间信息
        room_ptr rmp = _rm.get_room_by_uid(ssp->get_uid());
        if(rmp.get() == nullptr)
        {
            DBG_LOG("房间-没有找到房间信息！");

            resp_json["optype"] = "unknown";// 此时类型已经不重要
            resp_json["result"] = false;
            resp_json["reason"] = "获取房间信息失败！";
            return ws_send_resp(conn, resp_json);
        }
        // 3. 对消息进行反序列化
        Json::Value req_json;
        std::string req_body = msg->get_payload();
        bool ret = json_util::unserialize(req_body, &req_json);
        if(ret == false)
        {
            DBG_LOG("房间-反序列化请求失败！");

            resp_json["optype"] = "unknown";// 此时类型已经不重要
            resp_json["result"] = false;
            resp_json["reason"] = "请求解析失败！";
            return ws_send_resp(conn, resp_json);
        }
        DBG_LOG("房间-收到请求，开始处理...");
        // 4. 通过房间模块进行消息请求的处理
        rmp->handle_request(req_json);
    }
    /* websocket消息处理回调函数 */
    void wsmessage_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        // 1. 建立游戏大厅的长连接
        if (uri == "/hall")
        {
            wsmessage_game_hall(conn, msg);
        }
        // 2. 建立游戏房间的长连接
        else if (uri == "/room")
        {
            wsmessage_game_room(conn, msg);
        }
    }

private:
    std::string _web_root; // 静态资源根目录   如：/wwwroot/   接收请求：/register.html   得到资源路径：/wwwroot/register.html

    wsserver_t _wssvr;   // websocket服务器
    user_table _ut;      // 用户管理器
    online_user _olu;    // 在线用户管理器
    room_manager _rm;    // 房间管理器
    match_manage _mtm;   // 玩家匹配管理器
    session_manage _ssm; // session管理器
};

#endif